perm filename ENET.C[11,HE] blob sn#692231 filedate 1982-12-08 generic text, type C, neo UTF8
COMMENT ⊗   VALID 00016 PAGES
C REC  PAGE   DESCRIPTION
C00001 00001
C00002 00002	/*      enet.c  CMU     1/18/80 */
C00018 00003
C00020 00004
C00023 00005
C00028 00006
C00036 00007
C00039 00008
C00044 00009
C00048 00010
C00058 00011
C00061 00012
C00063 00013
C00066 00014
C00068 00015
C00072 00016
C00077 ENDMK
C⊗;
/*      enet.c  CMU     1/18/80 */

/*
 *  Ethernet interface driver
 *
 **********************************************************************
 * HISTORY
 * 10-Aug-82  Mike Accetta (mja) at Carnegie-Mellon University
 *	Added new EIOCMBIS and EIOCMBIC ioctl calls to set and clear
 *	bits in mode word;  added mode bit ENHOLDSIG which suppresses
 *	the resetting of an enabled signal after it is sent (to be
 *	used inconjunction with the SIGHOLD mechanism);  changed
 *	EIOCGETP to zero pad word for future compatibility;  changed enwrite()
 *	to enforce correct source host address on output packets (V3.05e).
 *
 * 01-Dec-81  Mike Accetta (mja) at Carnegie-Mellon University
 *	Fixed bug in timeout handling caused by missing "break" in the
 *	"switch" state check within enread().  This caused all reads
 *	to be preceeded by a bogus timeout.  In addition, fixed another
 *	bug in signal processing by also recording process ID of
 *	process to signal when an input packet is available.  This is
 *	necessary because it is possible for a process with an enabled
 *	signal to fork and exit with no guarantee that the child will
 *	reenable the signal.  Thus under appropriately bizarre race
 *	conditions, an incoming packet to the child can cause a signal
 *	to be sent to the unsuspecting process which inherited the
 *	process slot of the parent.  Of course, if the PID's wrap around
 *	AND the inheriting process has the same PID, well ... (V3.03d).
 *
 * 24-Nov-81  Mike Accetta (mja) at Carnegie-Mellon University
 *	Fixed bug in enrint() which failed to purge BDP before passing
 *	packet through input filters;  fixed bug in EIOCSETF which 
 *	failed to reinsert the changed filter at the proper position
 *	according to its priority;  added some additional counters to
 *	keep track of various packet counts (V3.03c);
 *
 * 18-Nov-81  Mike Accetta (mja) at Carnegie-Mellon University
 *	Fixed bug which failed to check for zero length writes
 *	in enwrite() (V3.03b).
 *
 * 07-Oct-81  Mike Accetta (mja) at Carnegie-Mellon University
 *	Substantially rewritten for 4.1BSD and incorporating most ideas
 *	from previous changes by Bob Fitzgerald and Jeff Mogul below
 *      as well as:
 *	- support for new autoconfigure procedure and multiple units
 *	  on multiple UBA's
 *	- UBA reset recovery
 *	- removal of 64K byte limit on buffers by using new ubaremap()
 *	  routine to remap a small fixed number of registers on each
 *        I/O (only one buffer pool is now used as well)
 *	- support for both user and kernel mode access at the open, read,
 *        write, ioctl and close levels
 *	- fix to untimeout bug which could leave many pending timeouts
 *	  hanging around after signals
 *	(V3.03a).
 *
 * 12-Aug-81  Bob Fitzgerald (rpf) at Carnegie-Mellon University
 *    -	Changed packet queueing mechanisms to allow more (8)
 *	packets to be queued for the same open device and the
 *	same packet to be queued for more than one open device
 *	simultaneously.  Done by changing the packet queueing
 *	mechanism from chaining through packet buffers to fixed
 *	size rings of packet buffer pointers.
 *    -	Added scavenging in case free buffer queue is depleted.
 *	This is guaranteed to create free buffers as long as
 *	MINSCAVENGE > 0 (and we are not losing packet buffers).
 *	Scavenging is done automatically in DeQueueRFree.
 *    -	Following Jeff Mogul@Stanford, split packet pool into
 *	separate transmit and receive pools, each of which is
 *	mapped onto the unibus independently.  This saves
 *	mapping each buffer twice (once in each direction),
 *	thereby doubling the number of packets possible.
 *    -	Simplified collision recovery mechanism (exponential
 *	backoff) by putting it in single centralized place
 *	rather than distributing it to each packet buffer.
 *    -	Added 3 ioctl calls:
 *	    EIOCSETW -- set maximum packet queueing for device
 *	    EIOCFLSH -- discard all packets queued for device
 *	    EIOCSTAT -- take a snapshot of status of all devices
 *    -	Fixed a minor bug in enopen.  It now checks for legality
 *	of minor device specification before checking to see
 *	if minor device is busy.
 *    - Converted OpenDescriptor.State -> OpenDescriptor.RecvState
 *	which is now an enumeration rather than a bit vector.
 *
 * 22-May-81  Bob Fitzgerald (rpf) at Carnegie-Mellon University
 *	Merged in some bug fixes due to Peter Eichenberger, installed
 *	by Jeffrey Mogul at Stanford.
 *		"Fixed unprotected critical sections which caused
 *		 garbage packets to be sent and received packets
 *		 to be lost.  Fixed bugs in check that UNIBUS
 *		 addresses fall within frst 64K bytes.  Added check
 *		 for illegal minor dev number in open."
 *
 * 23-Apr-81  Mike Accetta (mja)) at Carnegie-Mellon University
 *	Moved some structure declarations to enet.h to make them
 *	avialable to pstat (V2.00g).
 *
 * 01-Nov-80  Mike Accetta (mja) at Carnegie-Mellon University
 *	Upgraded for Berkeley version 3.34;  extracted NENET and
 *	ENADDR defintions onto en.h;  added support for FIONREAD ioctl
 *	call;  removed support for FIOCCHKR ioctl call;  added dummy
 *	reset routine for UNIBUS resets until they become a problem
 *	worth filling in the code for; fixed bug in recording process
 *	pointer for signals (V2.00).
 *
 * 04-Jun-80  Mike Accetta (mja) at Carnegie-Mellon University
 *	Changed to return immediately from read with EOF
 *	if no input packet is available and the timeout value is
 *	less than 0;  also added EIOCENBS and EIOCINHS ioctl calls
 *	to enable and inhibit signals when an input packet is
 *	received (V1.07f).
 *
 * 01-Apr-80  Mike Accetta (mja) at Carnegie-Mellon University
 *	Changed back to using separate BDP's for input and output
 *	packets; alsoo added check that alloacted unibus addresses fall
 *	within the first 64K bytes (V1.06b).
 *
 * 27-Feb-80  Mike Accetta (mja) at Carnegie-Mellon University
 *	Modified enread() to correctly interpret zero length reads from
 *	the FIOCCHKR ioctl call;  added EIOCGETP, EIOCSETP and EIOCSETF
 *	as synonyms for TIOCGETP, TIOCSETP, and TIOCSETD so that the
 *	tty versions may eventually be removed;  added check for minor
 *	device below HIPRIDEV when setting priorities above HIPRI
 *	(V1.05a).
 *
 * 22-Feb-80  Rick Rashid (rfr) at Carnegie-Mellon University
 *	Rewritten to provide multiple user access via user settable
 *	filters (V1.05).
 *
 * 18-Jan-80  Mike Accetta (mja) at Carnegie-Mellon University
 *      Created (V1.00).
 *
 **********************************************************************
 */

#include "enet.h"
#define	NEN	NENET
#if NEN > 0

#include "hparam.h"
#include "hsystm.h"
#include "hdir.h"
#include "huser.h"
#include "hmap.h"
#include "hpte.h"
#include "hbuf.h"
#include "hubar.h"
#include "hubav.h"
#include "hproc.h"
#include "htty.h"
#include "hqueue.h"
#include "henet.h"
#include "hconf.h"

#define DEBUG	3

#define	enprintf(flags)	if (enDebug&(flags)) printf

#define min(a,b)        ( ((a)<=(b)) ? (a) : (b) )

#define PRINET  26			/* interruptible */

/*
 *  'enQueueElts' is the pool of packet headers used by the driver.
 *  'enPackets'   is the pool of packets used by the driver (these should
 *	          be allocated dynamically when this becomes possible).
 *  'enFreeq'     is the queue of available packets
 *  'enState'     is the driver state table per logical unit number
 *  'enUnit'  	  is the physical unit number table per logical unit number;
 *		  the first "attach"ed ethernet is logical unit 0, etc.
 *  'enFreeqMin'  is the minimum number of packets ever in the free queue
 *		  (for statistics purposes)
 *  'enAllocCount'is the number of packets allocated to external kernel
 *		  applications
 *  'enScavenges' is the number of scavenges of the active input queues
 *		  (for statustics purposes)
 *  'enDebug'	  is a collection of debugging bits which enable trace and/or
 *		  diagnostic output as follows:
 *		  000001 - routine call trace (under DEBUG)
 *		  000002 - open descriptor queue trace (under DEBUG)
 *		  000004 - input error, collision and dropped packet
 *			   diagnostics
 */
struct enPacket	enQueueElts[ENPACKETS];
short		enPackets[ENPACKETS][ENPACKETSIZE];
struct enQueue	enFreeq;
struct enState  enState[NEN];
char		enUnit[NEN];
int		enFreeqMin = ENPACKETS;
int		enAllocCount = 0;
int		enScavenges = 0;
int		enDebug = 0;

/*
 *  Forward declarations for subroutines which return other
 *  than integer types.
 */
extern struct Queue *dequeue();
extern boolean enFilter();

/*
 *  Auto-configure declarations
 *  (note that the driver indirects physical unit numbers through a
 *  table to generate logical unit numbers.  This is basically used
 *  to allow one configuration file to be used on all systems while
 *  th`αtevice address may be at either of two locations.)
 */
extern int enprobe(), enattach(), enrint(), enxint();
struct	uba_device *endinfo[NEN];
u_short	enstd[] = { 0160020, 0164000 };
struct	uba_driver enetdriver =
	{ enprobe, 0, enattach, 0, enstd, "enet", endinfo };

/*
 *  forAllOpenDescriptors(p) -- a macro for iterating
 *  over all currently open devices.  Use it in place of
 *      "for ( ...; ... ; ... )"
 *  and supply your own loop body.  The loop variable is the
 *  parameter p which is set to point to the descriptor for
 *  each open device in turn.
 */

#define forAllOpenDescriptors(p)					\
	for ((p) = (struct enOpenDescriptor *)enDesq.enQ_F;		\
	      (struct Queue *)(&enDesq) != &((p)->enOD_Link);		\
	      (p) = (struct enOpenDescriptor *)(p)->enOD_Link.F)

/*
 *  enInitQueue - initialize ethernet queue
 */

#define	enInitQueue(q)							\
{									\
	initqueue((struct queue *)(q));					\
	(q)->enQ_NumQueued = 0;						\
}



/*
 *  enEnqueue - add an element to a queue
 */

#define	enEnqueue(q, elt)						\
{									\
	enqueue((struct queue *)(q), (struct queue *)(elt));		\
	(q)->enQ_NumQueued++;						\
}



/*
 *  enFlushQueue - release all packets from queue, freeing any
 *  whose reference counts drop to 0.  Assumes caller
 *  is at high IPL so that queue will not be modified while
 *  it is being flushed.
 */

enFlushQueue(q)
register struct enQueue *q;
{

    register struct enPacket *qelt;

    while((qelt=(struct enPacket *)dequeue((struct queue *)q)) != NULL)
    {
	if (0 == --(qelt->enP_RefCount))
	{
	    enEnqueue(&enFreeq, qelt);
	}
    }
    q->enQ_NumQueued = 0;

}

/*
 *  enInitWaitQueue - initialize an empty packet wait queue
 */

enInitWaitQueue(wq)
register struct enWaitQueue *wq;
{			

    wq->enWQ_Head = 0;
    wq->enWQ_Tail = 0;
    wq->enWQ_NumQueued = 0;
    wq->enWQ_MaxWaiting = ENDEFWAITING;

}



/*
 *  enEnWaitQueue - add a packet to a wait queue
 */

enEnWaitQueue(wq, p)
register struct enWaitQueue *wq;
struct enPacket *p;
{

    wq->enWQ_Packets[wq->enWQ_Tail] = p;
    wq->enWQ_NumQueued++;
    enNextWaitQueueIndex(wq->enWQ_Tail);

}



/*
 *  enDeWaitQueue - remove a packet from a wait queue
 */

struct enPacket *
enDeWaitQueue(wq)
register struct enWaitQueue *wq;
{

    struct enPacket *p;

    wq->enWQ_NumQueued--;
    if (wq->enWQ_NumQueued < 0)
	panic("enDeWaitQueue"); 
    p = wq->enWQ_Packets[wq->enWQ_Head];
    enNextWaitQueueIndex(wq->enWQ_Head);

    return(p);

}



/*
 *  enTrimWaitQueue - cut a wait queue back to size
 */
enTrimWaitQueue(wq, threshold)
register struct enWaitQueue *wq;
{

    register int Counter = (wq->enWQ_NumQueued - threshold);
    register struct enPacket *p;

#ifdef	DEBUG
    enprintf(1)("enTrimWaitQueue(%x, %d): %d\n", wq, threshold, Counter);
#endif
    while (Counter-- > 0)
    {
	wq->enWQ_NumQueued--;
	enPrevWaitQueueIndex(wq->enWQ_Tail);
	p = wq->enWQ_Packets[wq->enWQ_Tail];
	if (0 == --(p->enP_RefCount))
	{
	    enEnqueue(&enFreeq, p);
	}
    }
}



/*
 *  enFlushWaitQueue - remove all packets from wait queue
 */

#define	enFlushWaitQueue(wq)	enTrimWaitQueue(wq, 0)

/*
 *  scavenging thresholds:
 *
 *  index by number of active files;  for N open files, each queue may retain
 *  up to 1/Nth of the packets not guaranteed to be freed on scavenge.  The
 *  total number of available packets is computed less any which are currently
 *  allocated to other kernel applications and also less those currently
 *  on the transmit queues.
 *
 *  (assumes high IPL)
 */
char enScavLevel[(NEN*ENMAXOPENS)+1];

/*
 *  enInitScavenge -- set up ScavLevel table
 */
enInitScavenge()
{
    register int PoolSize = (ENPACKETS-enAllocCount-ENMINSCAVENGE);
    register int i = (NEN*ENMAXOPENS)+1;
    register struct enState *enStatep;

    for (enStatep=enState; enStatep < &enState[NEN]; enStatep++)
	PoolSize -= enXmitq.enQ_NumQueued;
    while (--i>0)
        enScavLevel[i] = (PoolSize / i);
}

/*
 *  enScavenge -- scan all OpenDescriptors for all ethernets, releasing
 *    any queued buffers beyond the prescribed limit and freeing any whose
 *    refcounts drop to 0.
 *    Assumes caller is at high IPL so that it is safe to modify the queues.
 */
enScavenge()
{

    register struct enOpenDescriptor *d;
    register int threshold = 0;
    register struct enState *enStatep;

    for (enStatep=enState; enStatep < &enState[NEN]; enStatep++)
	threshold += enCurOpens;
    threshold = enScavLevel[threshold];

    /* recalculate thresholds based on current allocations */
    enInitScavenge();

    enScavenges++;
#ifdef	DEBUG
    enprintf(1)("enScavenge: %d\n", threshold);
#endif
    for (enStatep=enState; enStatep < &enState[NEN]; enStatep++)
    {
	if (enDesq.enQ_F == 0)
	    continue;			/* never initialized */
	forAllOpenDescriptors(d)
	{
	    enTrimWaitQueue(&(d->enOD_Waiting), threshold);
	}
    }

}



/*
 *  enAllocatePacket - allocate the next packet from the free list
 *
 *  Assumes IPL is at high priority so that it is safe to touch the
 *  packet queue.  If the queue is currently empty, scavenge for
 *  more packets.
 */

struct enPacket *
enAllocatePacket()
{

    register struct enPacket *p;

    if (0 == enFreeq.enQ_NumQueued)
	enScavenge();
    p = (struct enPacket *)dequeue((struct queue *)&enFreeq);
    if (p == NULL)
	panic("enAllocatePacket");
    if (enFreeqMin > --enFreeq.enQ_NumQueued)
	enFreeqMin = enFreeq.enQ_NumQueued;

    return(p);

}



/*
 *  enDeallocatePacket - place the packet back on the free packet queue
 *
 *  (High IPL assumed).
 */

#define	enDeallocatePacket(p)						\
{									\
	enqueue((struct queue *)&enFreeq, (struct queue *)(p));		\
	enFreeq.enQ_NumQueued++;					\
}

/*
 *  enopen - open ether net device
 *
 *  Callable from user or kernel mode.  Bit ENKERNEL of the flag argument
 *  indicates whether or not the open is being done by the kernel.  This
 *  bit is remembered for later use by read, write, and ioctl.
 *
 *  Errors:	ENXIO	- illegal minor device number
 *		EBUSY	- minor device already in use
 *		ENOMEM	- unable to allocate low 64K UBA memory
 */

/* ARGSUSED */
enopen(dev, flag)
{
    register int md = ENINDEX(dev);
    register struct enState *enStatep;
    register struct uba_device *ui;
    int ipl;

    /*
     * Each open enet file has a different minor device number.
     * In general, a prospective net user must try successively
     * to open the devices 'enet', 'enet1', ... 'enetn' where n
     * is MAXOPENS-1.  This is not elegant, but UNIX will call
     * open for each new open file using the same inode but calls
     * close only when the last open file referring to the inode 
     * is released. This means that we cannot know inside the
     * driver code when the resources associated with a particular
     * open of the same inode should be deallocated.  Thus, we have
     * simply made up a number of different inodes to represent the
     * simultaneous opens of the ethernet.  Each of these has
     * a different minor device number.
     *
     * When opening an ethernet device in the kernel, simply iterate
     * through minor device numbers until an open fails to return
     * EBUSY.  
     */

#ifdef	DEBUG
    enprintf(1)("enopen(%d, %x):\n", md, flag);
#endif
    /* check for illegal minor dev */
    if (md >= ENMAXOPENS
        || (ui=endinfo[ENUNIT(dev)]) == 0
	|| ui->ui_alive == 0)
    {
	u.u_error = ENXIO;
	return;
    }
    enStatep = &enState[ENUNIT(dev)];
    if (enOpenFlag[md])
    {
        u.u_error = EBUSY;
	return;
    }
    enOpenFlag[md] = TRUE;
    /*  initialize unit on first open  */
    if (enDesq.enQ_F == 0)
    {
	enInit(enStatep, ui);
	if (u.u_error)
	{
	    enOpenFlag[md] = FALSE;
	    return;
	}
    }
    ipl = spl6();
    if (enRP == NULL)
	enrstart(ui);
    splx(ipl);
    enprintf(2)("enopen: Desq: %x, %x\n", enDesq.enQ_F, enDesq.enQ_B);
    enInitDescriptor(&enAllDescriptors[md], flag);
    enInsertDescriptor(&(enDesq), &enAllDescriptors[md]);

}


/*
 *  enInit - intialize ethernet unit
 */

enInit(enStatep, ui)
register struct enState *enStatep;
register struct uba_device *ui;
{

#ifdef	DEBUG
    enprintf(1)("enInit(%x, %x):\n", enStatep, ui);
#endif
    /*
     * Allocate unibus BDP and mapping registers to be used for all
     * subsequent transfers.  The mapped address is arbitrary since
     * we remap every I/O.  The low order (byte offset) bits must be zero,
     * however, since ubaremap() neglects to clear them from the old
     * resource values when remapping.
     *
     * Since the ethernet interface ignores the extended
     * memory address bits, we must insure that the
     * allocated portions of the unibus map are all in
     * the first 64K before proceeding.
     *
     * We can't afford to sleep in uballoc() since someone else might
     * come along and open another minor device before we finished
     * the initialization.  We could fix this by blocking opens
     * while initializing but this hardly seems worth it since if
     * UBA resources are so scare, chances are good we won't get
     * allocated to the low 64K anyway.
     */

    enRinfo = uballoc(ui->ui_ubanum, (caddr_t)0x80000000, ENUBAALLOCSIZE, UBA_NEEDBDP|UBA_CANTWAIT);
    enXinfo = uballoc(ui->ui_ubanum, (caddr_t)0x80000000, ENUBAALLOCSIZE, UBA_NEEDBDP|UBA_CANTWAIT);
    /*
     * check that the ENDs of the UNIBUS spaces allocated
     * are both inside the 64K byte addressing limit of the
     * interface
     */
    if ( ((enRinfo + ENUBAALLOCSIZE)&0x30000) ||
	 ((enXinfo + ENUBAALLOCSIZE)&0x30000) ||
	 enRinfo == 0 || enXinfo == 0 )
    {
	ubarelse(ui->ui_ubanum, &enRinfo);	/* ubarelse() allows these to
	ubarelse(ui->ui_ubanum, &enXinfo);	 * be zero and just returns */
	u.u_error = ENOMEM;
	return;
    }
    enRerrors = 0;
    enXerrors = 0;
    enRand = time;
    enXP = NULL;
    enRP = NULL;
    /*  initialize free queue on first unit  */
    if (enFreeq.enQ_F == 0)
    {
	register int i;

	initqueue((struct queue *)&enFreeq);
	for (i=0; i<ENPACKETS; i++)
	{
	    register struct enPacket *p;

	    p = &enQueueElts[i];
	    p->enP_Func = NULL;
	    p->enP_RefCount = 0;
	    p->enP_Data = (u_short *)&enPackets[i][0];
	    enDeallocatePacket(p);
	}
    }
    initqueue((struct queue *)&enXmitq);
    initqueue((struct queue *)&enDesq);

}        

/*
 *  enclose - ether net device close routine
 *
 *  Callable from user or kernel mode.
 */

/* ARGSUSED */
enclose(dev, flag)
{
    register int md = ENINDEX(dev);
    register struct enState *enStatep = &enState[ENUNIT(dev)];
    register struct uba_device *ui = endinfo[ENUNIT(dev)];
    register struct enOpenDescriptor *d = &enAllDescriptors[md];
    int ipl;

    enOpenFlag[md] = FALSE;      

#ifdef	DEBUG
    enprintf(1)("enclose(%d, %x):\n", md, flag);
#endif

    /*
     *  insure that receiver doesn't try to queue something
     *  for the device as we are decommissioning it.
     *  (I don't think this is necessary, but I'm a coward.)
     */
    ipl = spl6();
    dequeue((struct queue *)d->enOD_Link.B);
    enCurOpens--;
    enprintf(2)("enclose: Desq: %x, %x\n", enDesq.enQ_F, enDesq.enQ_B);
    enFlushWaitQueue(&(d->enOD_Waiting));
    splx(ipl);

    if (enCurOpens == 0)
    {
        /*
	 *  All done. No enet files are now open.  We can
         *  close down shop completely.  We don't bother to
	 *  deallocate the UNIBUS resources though, since
	 *  they are short and they may not get allocated in
	 *  the first 64K bytes next time around.
  	 */
	ipl = spl6();
        ENADDR->enrcsr = 0;
        ENADDR->enxcsr = 0;
	enFlushQueue(&enXmitq);
	if (enXP != NULL)
	{
	    enDeallocatePacket(enXP);
	    enXP = NULL;
	}
	if (enRP != NULL)
	{
	    enDeallocatePacket(enRP);
	    enRP = NULL;
	}
	splx(ipl);
    }

}

/*
 *  enread - read next packet from net
 *
 *  Callable from user or kernel mode (checks OD_Flag)
 */

/* VARARGS */
enread(dev, pp)
dev_t dev;
struct enPacket **pp;
{

    register struct enState *enStatep = &enState[ENUNIT(dev)];
    register struct enOpenDescriptor *d = &enAllDescriptors[ENINDEX(dev)];
    register struct enPacket *p;
    int ipl;
    extern enTimeout(), enrstart();

#if	DEBUG
    enprintf(1)("enread(%x):", dev);
#endif

    ipl = spl6();
    /*
     *  If nothing is on the queue of packets waiting for
     *  this open enet file, then set timer and sleep until
     *  either the timeout has occurred or a packet has
     *  arrived.
     */

    while (0 == d->enOD_Waiting.enWQ_NumQueued)
    {
	if (d->enOD_Timeout < 0)
	{
	    splx(ipl);
	    return;
	}
        if (d->enOD_Timeout)
	{
	    /*
	     *  If there was a previous timeout pending for this file,
	     *  cancel it before setting another.  This is necessary since
	     *  a cancel after the sleep might never happen if the read is
	     *  interrupted by a signal.
	     */
	    if (d->enOD_RecvState == ENRECVTIMING)
		untimeout(enTimeout, (caddr_t)d);
            timeout(enTimeout, (caddr_t)d, d->enOD_Timeout);
            d->enOD_RecvState = ENRECVTIMING;
	}
        else
            d->enOD_RecvState = ENRECVIDLE;

        sleep((caddr_t)d, PRINET);

        switch (d->enOD_RecvState)
	{
            case ENRECVTIMING:
	    {
                untimeout(enTimeout, (caddr_t)d);
                d->enOD_RecvState = ENRECVIDLE;
		break;
	    }
            case ENRECVTIMEDOUT:
	    {
                splx(ipl);
		return;
	    }
	}
    }

    p = enDeWaitQueue(&(d->enOD_Waiting));
    splx(ipl);

    /*
     * Kernel mode read
     *
     * Return pointer to packet.  It must be subsequently freed via an
     * EIOCDEALLOCP ioctl() call and may not be changed since it may
     * have also been on some other wait queue.
     *
     * (we don't use any fields of the U area since we can't guarantee
     * our context).
     */
    if (d->enOD_Flag & ENKERNEL)
    {
	enAllocCount++;		/* packet allocated to kernel */
	*pp = p;
	return;
    }

    /*  
     * User mode read
     *
     * Move data from packet into user space.  Throw away
     * any data left over if u.u_count is less than 
     * the number of 16-bit words in the packet.
     */

    iomove((caddr_t)(p->enP_Data),
            min((p->enP_WordCount)<<1, u.u_count), 
            B_READ);

    ipl = spl6();
    if (0 == --(p->enP_RefCount))
    {
	enDeallocatePacket(p);	/* else release buffer */
    }
    splx(ipl);

}

/*
 *  enwrite - write next packet to net
 *
 *  Callable from user or kernel mode (checks OD_Flag).
 */

/* VARARGS */
enwrite(dev, ep)
dev_t dev;
register struct enPacket *ep;
{
    register struct enState *enStatep = &enState[ENUNIT(dev)];
    register struct enOpenDescriptor *d = &enAllDescriptors[ENINDEX(dev)];
    register struct uba_device *ui = endinfo[ENUNIT(dev)];
    register struct enPacket *p;
    register u_int ByteCount;
    int ipl;
    extern enxstart();

#if	DEBUG
    enprintf(1)("enwrite(%x):\n", dev);
#endif

    /*
     * User mode write
     *
     * Allocate packet for transmit.  Block if too many transmit packets
     * have already been allocated until some are freed.
     *
     * Copy user data into packet (if ubaremap() handled user page tables
     * we might be able to get away with direct mapping without copying here).
     */
    if ((d->enOD_Flag&ENKERNEL) == 0)
    {
	if (u.u_count == 0)
	    return;
	ipl = spl6();
	while (enXmitq.enQ_NumQueued >= ENXPACKETS)
	    sleep((caddr_t)&enXmitq, PRINET);
	p = enAllocatePacket();
	p->enP_RefCount++;
	splx(ipl);

	iomove((caddr_t)(p->enP_Data),
	       ByteCount=min(ENPACKETSIZE<<1, u.u_count), 
	       B_WRITE);

	p->enP_WordCount = (ByteCount+1)>>1;
    }
    else
    /*
     * Kernel mode write
     *
     * Use packet supplied by caller.
     *
     * (Again, we avoid using any fields of the U area since we may be called
     * in interrupt context).
     */
    {
	p = ep;
    }

    /*
     *  Enforce correct source host
     */
    *((u_char *)(p->enP_Data)) = ~(ENADDR->enaddr);

    ipl = spl6();
    if (enXmitqMax < ++enXmitq.enQ_NumQueued)
	enXmitqMax = enXmitq.enQ_NumQueued;
    if (NULL == enXP)		/* if transmitter is idle */
    {
	enXmitq.enQ_NumQueued--;
	enXP = p;			/* use buffer to */
	enMask = -1;
	enxstart(ui);			/* start write   */
    }
    else
        enqueue((struct queue *)&enXmitq, (struct queue *)p); /* just queue it */
    splx(ipl);

}

/*
 *  enioctl - ether net control
 *
 *  Callable from user or kernel mode (checks OD_Flag)
 *
 *  EIOCGETP	 - get ethernet parameters
 *  EIOCSETP	 - set ethernet read timeout
 *  EIOCSETF	 - set ethernet read filter
 *  EIOCENBS	 - enable signal when read packet available
 *  EIOCINHS     - inhibit signal when read packet available
 *  FIONREAD	 - check for read packet available
 *  EIOCSETW	 - set maximum read packet waiting queue length
 *  EIOCFLUSH	 - flush read packet waiting queue
 *  EIOCALLOCP	 - allocate packet (kernel only)
 *  EIOCDEALLOCP - deallocate packet (kernel only)
 *
 *  encopyin()  copies data from caller to driver based on mode of open
 *  encopyout() copies data from driver to caller based on mode of open
 */

#define	encopyin(from, to, size)					\
	if (d->enOD_Flag&ENKERNEL)					\
		bcopy(from, to, size);					\
	else								\
		if (copyin(from, to, size))				\
		{							\
			u.u_error = EFAULT;				\
			return;						\
		}

#define	encopyout(from, to, size)					\
	if (d->enOD_Flag&ENKERNEL)					\
		bcopy(from, to, size);					\
	else								\
		if (copyout(from, to, size))				\
		{							\
			u.u_error = EFAULT;				\
			return;						\
		}

/* ARGSUSED */
enioctl(dev, cmd, addr, flag)
caddr_t addr;
dev_t flag;
{

    register struct uba_device *ui = endinfo[ENUNIT(dev)];
    register struct enState *enStatep = &enState[ENUNIT(dev)];
    register struct enOpenDescriptor * d = &enAllDescriptors[ENINDEX(dev)];
    int ipl;

#if	DEBUG
    enprintf(1)("enioctl(%x, %x, %x, %x):\n", dev, cmd, addr, flag);
#endif

    switch (cmd)
    {
	case EIOCGETP:
	{
            struct eniocb t;

	    t.en_maxwaiting = ENMAXWAITING;
	    t.en_maxpriority = (ENINDEX(dev) < ENHIPRIDEV)?(ENHIPRI-1):ENMAXPRI;
            t.en_rtout = d->enOD_Timeout;
            t.en_addr = ~(ENADDR->enaddr);
	    t.en_maxfilters = ENMAXFILTERS;
	    t.en_pad1 = 0;			/* for future expansion */

            encopyout((caddr_t)&t, addr, sizeof t);
	}
        endcase

        case EIOCSETP:
	{
            struct eniocb t;

            encopyin(addr, (caddr_t)&t, sizeof t);
            d->enOD_Timeout = t.en_rtout;
	}
        endcase

        case EIOCSETF:
	{
            struct enfilter f;

            encopyin(addr, (caddr_t)&f, sizeof f);
	    if ((ENINDEX(dev) < ENHIPRIDEV && f.enf_Priority >= ENHIPRI) ||
		f.enf_FilterLen > ENMAXFILTERS)
	    {
		u.u_error = EINVAL;
		return;
	    }
            /* insure that filter is installed indivisibly */
            ipl = spl6();
            bcopy((caddr_t)&f, (caddr_t)&(d->enOD_OpenFilter), sizeof f);
	    dequeue((struct queue *)d->enOD_Link.B);
	    enDesq.enQ_NumQueued--;
	    enInsertDescriptor(&(enDesq), d);
            splx(ipl);
	}
        endcase

	/*
	 *  Enable signal n on input packet
	 */
	case EIOCENBS:
	{
            union {int n;  int (*func)();} un;

	    encopyin(addr, (caddr_t)&un, sizeof un);
	    if ((d->enOD_Flag & ENKERNEL) == 0)
	    {
		if (un.n < NSIG)
		{
		    d->enOD_SigProc = u.u_procp;
		    d->enOD_SigPid  = u.u_procp->p_pid;
		    d->enOD_SigNumb = un.n;	/* This must be set last */
		}
		else
		{
		    goto bad;
		}
	    }
	    else
	    {
		d->enOD_SigFunc = un.func;
		d->enOD_SigNumb = NSIG;		/* This must be set last */
	    }
	}
	endcase

	/*
	 *  Disable signal on input packet
	 */
	case EIOCINHS:
	{
	    if ((d->enOD_Flag & ENKERNEL) == 0)
	    {
		d->enOD_SigNumb = 0;
	    }
	    else
	    {
		d->enOD_SigFunc = NULL;
	    }
	}
	endcase

	/*
	 *  Check for packet waiting
	 */
	case FIONREAD:
	{
            int n;
            register struct enWaitQueue *wq;

	    ipl = spl6();
	    if ((wq = &(d->enOD_Waiting))->enWQ_NumQueued)
		n = (wq->enWQ_Packets[wq->enWQ_Head]->enP_WordCount)<<1;
	    else
		n = 0;
	    splx(ipl);
	    encopyout((caddr_t)&n, addr, sizeof n);
	}
	endcase

	/*
	 *  Set maximum recv queue length for a device
	 */
	case EIOCSETW:
	{
            unsigned un;

	    encopyin(addr, (caddr_t)&un, sizeof un);
	    /*
             *  unsigned un         MaxQueued
             * ----------------    ------------
             *  0               ->  DEFWAITING
	     *  1..MAXWAITING   ->  un
	     *  MAXWAITING..-1  ->  MAXWAITING
             */
	    d->enOD_Waiting.enWQ_MaxWaiting = (un) ? min(un, ENMAXWAITING)
                                        : ENDEFWAITING;
	}
	endcase

	/*
	 *  Flush all packets queued for a device
	 */
	case EIOCFLUSH:
	{
	    ipl = spl6();
	    enFlushWaitQueue(&(d->enOD_Waiting));
	    splx(ipl);
	}
	endcase

	/*
	 *  Set mode bits
	 */
	case EIOCMBIS:
	{
	    u_short mode;

	    encopyin(addr, (caddr_t)&mode, sizeof mode);
	    if (mode&ENPRIVMODES)
		u.u_error = EINVAL;
	    else
		d->enOD_Flag |= mode;
	    break;
	}

	/*
	 *  Clear mode bits
	 */
	case EIOCMBIC:
	{
	    u_short mode;

	    encopyin(addr, (caddr_t)&mode, sizeof mode);
	    if (mode&ENPRIVMODES)
		u.u_error = EINVAL;
	    else
		d->enOD_Flag &= ~mode;
	    break;
	}

	/*
	 *  Allocate an ethernet packet (kernel only)
	 */
	case EIOCALLOCP:
	{
	    register struct enPacket *p;

	    if ((d->enOD_Flag&ENKERNEL) == 0)
		goto bad;
	    ipl = spl6();
	    p = enAllocatePacket();
	    p->enP_RefCount++;
	    enAllocCount++;
	    splx(ipl);
	    *(struct enPacket **)addr = p;
	}
	endcase

	/*
	 *  Deallocate an ethernet packet (kernel only)
	 */
	case EIOCDEALLOCP:
 	{
	    register struct enPacket *p;

	    if ((d->enOD_Flag & ENKERNEL) == 0)
		goto bad;
	    p = *(struct enPacket **)addr;
	    ipl = spl6();
	    enAllocCount--;
	    if (--(p->enP_RefCount) == 0)
		enDeallocatePacket(p);
	    splx(ipl);
	}
	endcase

        default:
	{
	bad:
            u.u_error = EINVAL;
	}
        endcase
    }

}



/*
 *  enTimeout - process ethernet read timeout
 */

enTimeout(d)
struct enOpenDescriptor * d;
{
    register int ipl;

#ifdef	DEBUG
    enprintf(1)("enTimeout(%x):\n", d);
#endif
    ipl = spl6();
    d->enOD_RecvState = ENRECVTIMEDOUT;
    wakeup((caddr_t)d);
    splx(ipl);

}

/*
 *  enrstart - start read operation on net
 */

enrstart(ui)
register struct uba_device *ui;
{

    register struct enState *enStatep = &enState[ui->ui_unit];

#if	DEBUG
    enprintf(1)("enrstart(%x):\n", ui);
#endif
    /*
     *  We are only called when priority is >= 6 or when
     *  receiver is inactive (during initialization).
     *  So it is safe to go get a free buffer.
     */
    if (NULL == enRP)
    {
	enRP = enAllocatePacket();
    }

    ENADDR->enrwc = -ENPACKETSIZE;
    ENADDR->enrba = ubaremap(ui->ui_ubanum, (unsigned)enRinfo, (caddr_t)enRP->enP_Data);
    ENADDR->enrcsr = ENCSR_IE|ENCSR_GO;

#if	DEBUG
    enprintf(1)("enrstarted\n");
#endif

}

/*
 * enxstart - start packet transmission on net
 */

enxstart(ui)
register struct uba_device *ui;
{

    register struct enState *enStatep = &enState[ui->ui_unit];

#ifdef	DEBUG
    enprintf(1)("enxstart(%x):\n", ui);
#endif

    /*
     *  Synchronization not needed here because only transmitter
     *  touches anything we touch, and only if it is active, in
     *  which case we wouldn't be here.
     */
    if (NULL == enXP)
    {
	enXP = (struct enPacket *)dequeue((struct queue *)&enXmitq);
	if (enXP == NULL)
	    return;
	if (enXmitq.enQ_NumQueued-- == ENXPACKETS)
	   wakeup((caddr_t)&enXmitq);
	enMask = -1;			/* first try with new pkt */
    }

    ENADDR->enxwc = -(enXP->enP_WordCount);
    ENADDR->enxba = ubaremap(ui->ui_ubanum, (unsigned)enXinfo, (caddr_t)enXP->enP_Data);
    ENADDR->enxdly = enRand & ~enMask;
    ENADDR->enxcsr = ENCSR_IE|ENCSR_GO;

#ifdef	DEBUG
    enprintf(1)("enxstarted\n");
#endif

}

/*
 * enrint - net read interrupt handler
 */

enrint(en)
{

    register struct enState *enStatep = &enState[en];
    register struct uba_device *ui = endinfo[en];
    register struct enPacket *p;

#if	DEBUG
    enprintf(1)("enrint(%d):\n", en);
#endif

    if (NULL == (p=enRP))
    {
	printf("enet%d: spurious input interrupt\n", en);
	return;
    }

    /*
     * We don't need to do a purge here since the ubaremap() will do one
     * when the next read is started (this works as long as the read
     * is started before passing the packet through the filters where
     * the incomplete buffer might cause the packet to be dropped).
     */
    if (ENADDR->enrcsr&ENCSR_ERR)
    {
        enRerrors++;
	enprintf(4)("en%d: bad packet\n", en);
        enrstart(ui);
        return;
    }
    enRP = NULL;

    p->enP_WordCount = (ENPACKETSIZE + ENADDR->enrwc)&01777;
    enrstart(ui);

    enInputDone(enStatep, p);

}

/*
 * enInputDone - process correctly received packet
 */

enInputDone(enStatep, p)
register struct enState *enStatep;
register struct enPacket *p;
{
    register struct enOpenDescriptor *d;

#if	DEBUG
    enprintf(1)("enInputDone(%x): %x\n", enStatep, p);
#endif
    forAllOpenDescriptors(d)
    {
        if (enFilter(p,d))
	{
            if (d->enOD_Waiting.enWQ_NumQueued < d->enOD_Waiting.enWQ_MaxWaiting)
	    {
                enEnWaitQueue(&(d->enOD_Waiting), p);
                p->enP_RefCount++;
                wakeup((caddr_t)d);
#if	DEBUG
                enprintf(1)("enInputDone: queued\n");
#endif
	    }
	    /*  send notification when input packet received  */
	    if (d->enOD_SigNumb)
	    {
		if (d->enOD_SigNumb < NSIG)
		{
		    if (d->enOD_SigProc->p_pid == d->enOD_SigPid)
			psignal(d->enOD_SigProc, d->enOD_SigNumb);
		    if ((d->enOD_Flag & ENHOLDSIG) == 0)
			d->enOD_SigNumb = 0;		/* disable signal */
		}
		else
		{
		    (*(d->enOD_SigFunc))();
		}
	    }
	    if (d->enOD_OpenFilter.enf_Priority >= ENHIPRI)
		break;
	}
    }
    if (p->enP_RefCount == 0)			/* this buffer no longer in */
    {
	enDeallocatePacket(p);			/*  use; return to free queue */
	enRdrops++;
    }
    else
	enRcnt++;

}

/*
 * enxint - net transmit interrupt handler
 */

enxint(en)
{

    register struct enState *enStatep = &enState[en];
    register struct uba_device *ui = endinfo[en];

#if	DEBUG
    enprintf(1)("enxint(%d):\n", en);
#endif

    if (NULL == enXP)
    {
	printf("enet%d: spurious transmit interrupt\n", en);
	return;
    }
     
    /*
     * We no longer need to do a purge here since the ubaremap()
     * when the next packet is transmitted will do one for us.
     */
    if ((ENADDR->enxcsr&ENCSR_ERR))
    {
	enXerrors++;
	enprintf(4)("enet%d: collision\n", en);
        if (enMask)			/* if < 16 retransmissions */
	{
            enMask <<= 1;		    /* ~double delay and try again */
            enxstart(ui);
            return;
	}
        else
	{			    /* give up on this pkt */
	    enXdrops++;
	    enprintf(4)("enet%d: dropped packet\n", en);
	}
    }
    else
	enXcnt++;

    if (enXP->enP_Func != NULL)
	(*(enXP->enP_Func))(enXP);
    else if (--(enXP->enP_RefCount) == 0)
	enDeallocatePacket(enXP);
    enXP = NULL;
    enxstart(ui);

}

enInitDescriptor(d, flag)
register struct enOpenDescriptor *d;
{

#if	DEBUG
    enprintf(1)("enInitDescriptor(%x):\n", d);
#endif
    d->enOD_RecvState = ENRECVIDLE;
    d->enOD_OpenFilter.enf_FilterLen = 0;
    d->enOD_OpenFilter.enf_Priority = 0;
    d->enOD_Timeout = 0;
    d->enOD_SigNumb = 0;
    d->enOD_Flag = (flag&ENKERNEL);
    enInitWaitQueue(&(d->enOD_Waiting));
#if	DEBUG
    enprintf(1)("=>eninitdescriptor\n");
#endif

}

#define	opx(i)	(i>>ENF_NBPA)

boolean
enFilter(p,d)
struct enPacket *p;
struct enOpenDescriptor *d;
{

    register unsigned short *sp;
    register unsigned short *fp;
    register unsigned short *fpe;
    register unsigned op;
    register unsigned arg;
    register maxword;
    unsigned short stack[ENMAXFILTERS+1];
    struct fw {unsigned arg:ENF_NBPA, op:ENF_NBPO;};

#ifdef	DEBUG
    enprintf(1)("enFilter(%x,%x):\n", p, d);
#endif
    sp = &stack[ENMAXFILTERS];
    maxword = p->enP_WordCount+ENF_PUSHWORD;
    fp = &d->enOD_OpenFilter.enf_Filter[0];
    fpe = &fp[d->enOD_OpenFilter.enf_FilterLen];
    *sp = TRUE;

    for (; fp < fpe; )
    {
	op = ((struct fw *)fp)->op;
	arg = ((struct fw *)fp)->arg;
	fp++;
	switch (arg)
	{
	    default:
		if ((arg >= ENF_PUSHWORD)&&(arg < maxword))
		    *--sp = p->enP_Data[arg-ENF_PUSHWORD];
		else
		{
#ifdef	DEBUG
		    enprintf(1)("=>0(len)\n");
#endif
		    return(false);
		}
		break;
	    case ENF_PUSHLIT:
		*--sp = *fp++;
		break;
	    case ENF_PUSHZERO:
		*--sp = 0;
	    case ENF_NOPUSH:
		break;
	}
	if (op == ENF_NOP)
	    continue;
	if (sp > &stack[ENMAXFILTERS-2])
	{
#ifdef	DEBUG
	    enprintf(1)("=>0(sp)\n");
#endif
	    return(false);
	}
	arg = *sp++;
	switch (op)
	{
	    default:
#ifdef	DEBUG
		enprintf(1)("=>0(def)\n");
#endif
		return(false);
	    case opx(ENF_AND):
		*sp &= arg;
		break;
	    case opx(ENF_OR):
		*sp |= arg;
		break;
	    case opx(ENF_XOR):
		*sp ↑= arg;
		break;
	    case opx(ENF_EQ):
		*sp = (*sp == arg);
		break;
	    case opx(ENF_LT):
		*sp = (*sp < arg);
		break;
	    case opx(ENF_LE):
		*sp = (*sp <= arg);
		break;
	    case opx(ENF_GT):
		*sp = (*sp > arg);
		break;
	    case opx(ENF_GE):
		*sp = (*sp >= arg);
		break;
	}
    }
#ifdef	DEBUG
    enprintf(1)("=>%x\n", *sp);
#endif
    return((boolean)*sp);

}

/*
 *  enInsertDescriptor - insert open descriptor in queue ordered by priority
 */

enInsertDescriptor(q, d)
register struct enQueue *q;
register struct enOpenDescriptor *d;
{
    struct enOpenDescriptor * nxt;
    register int ipl;

    ipl = spl6();
    nxt = (struct enOpenDescriptor *)q->enQ_F;
    while ((struct Queue *)q != &(nxt->enOD_Link))
    {
        if (d->enOD_OpenFilter.enf_Priority > nxt->enOD_OpenFilter.enf_Priority)
	    break;
        nxt = (struct enOpenDescriptor *)nxt->enOD_Link.F;
    }
    enqueue((struct queue *)&(nxt->enOD_Link),(struct queue *)&(d->enOD_Link));
    enprintf(2)("enID: Desq: %x, %x\n", q->enQ_F, q->enQ_B);
    q->enQ_NumQueued++;
    splx(ipl);

}


enattach(ui)
register struct uba_device *ui;
{

    static int enunit = 0;

    enUnit[enunit++] = ui->ui_unit;
	
}

enprobe(reg)
caddr_t	reg;
{
	register int br,cvec;
	
#ifdef	lint
	br = 0; cvec = br; br = cvec;
#endif
	
	/*
	 * This sends a very small garbage packet out on the net.  Hope
	 * nobody minds.
	 */
	((struct enreg *)reg)->enxwc = -1;
	((struct enreg *)reg)->enxba = 0;
	((struct enreg *)reg)->enxcsr = ENCSR_IE|ENCSR_GO;
	DELAY(10000);
	((struct enreg *)reg)->enxcsr = 0;

	/*
	 * In case of collision, wrong vector will be generated.  Always
	 * strip off low 4 bits to get base vector address (this means that
	 * the ethernet board must be set to interrupt at a quadlongword
	 * address).
	 */
	cvec &= ~0xf;		/* in case of collision interrupt */
	return(1);

}


/*
 * enreset - reset the ethernet
 *
 * Called on UBA reset to restart pending I/O.
 *
 * Just prints the device name and restarts the receiver and
 * transmitter.  No other state that we care about is lost by
 * the reset.
 */

enreset(uban)
{

    register int en;
    register struct uba_device *ui;

    for (en = 0; en < NEN; en++)
    {
	if ((ui = endinfo[en]) == 0 || ui->ui_alive == 0 ||
	    ui->ui_ubanum != uban)
		continue;
	printf(" enet%d", en);

	if (enState[en].ens_Desq.enQ_F == 0)
	    continue;			/* never initialized */

	/*
	 * Normally we would free the UBA resources here.  Since we
	 * don't care that the UBA reset has invalidated the mapping
	 * registers (because we remap each ethernet I/O anyway),
	 * we can safely ignore this and continue knowing that we still
	 * have the same mapping registers allocated in the first 64K bytes.
	 */

	/* Restart the receiver */
	enrstart(ui);

	/* Restart the transmitter (if necessary) */
	enxstart(ui);
    }

}
#endif